﻿/*
 * Created by SharpDevelop.
 * User: issuser
 * Date: 2010-12-7
 * Time: 11:21
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Data;
using System.Text;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using NLite.Reflection;

namespace NLite.Data.SqlTypes
{
    public enum DbTypeCode
    {
        AnsiString = 0,
        //
        // Summary:
        //     A variable-length stream of binary data ranging between 1 and 8,000 bytes.
        Binary = 1,
        //
        // Summary:
        //     An 8-bit unsigned integer ranging in value from 0 to 255.
        Byte = 2,
        //
        // Summary:
        //     A simple type representing Boolean values of true or false.
        Boolean = 3,
        //
        // Summary:
        //     A currency value ranging from -2 63 (or -922,337,203,685,477.5808) to 2 63
        //     -1 (or +922,337,203,685,477.5807) with an accuracy to a ten-thousandth of
        //     a currency unit.
        Currency = 4,
        //
        // Summary:
        //     A type representing a date value.
        Date = 5,
        //
        // Summary:
        //     A type representing a date and time value.
        DateTime = 6,
        //
        // Summary:
        //     A simple type representing values ranging from 1.0 x 10 -28 to approximately
        //     7.9 x 10 28 with 28-29 significant digits.
        Decimal = 7,
        //
        // Summary:
        //     A floating point type representing values ranging from approximately 5.0
        //     x 10 -324 to 1.7 x 10 308 with a precision of 15-16 digits.
        Double = 8,
        //
        // Summary:
        //     A globally unique identifier (or GUID).
        Guid = 9,
        //
        // Summary:
        //     An integral type representing signed 16-bit integers with values between
        //     -32768 and 32767.
        Int16 = 10,
        //
        // Summary:
        //     An integral type representing signed 32-bit integers with values between
        //     -2147483648 and 2147483647.
        Int32 = 11,
        //
        // Summary:
        //     An integral type representing signed 64-bit integers with values between
        //     -9223372036854775808 and 9223372036854775807.
        Int64 = 12,
        //
        // Summary:
        //     A general type representing any reference or value type not explicitly represented
        //     by another DbType value.
        Object = 13,
        //
        // Summary:
        //     An integral type representing signed 8-bit integers with values between -128
        //     and 127.
        SByte = 14,
        //
        // Summary:
        //     A floating point type representing values ranging from approximately 1.5
        //     x 10 -45 to 3.4 x 10 38 with a precision of 7 digits.
        Single = 15,
        //
        // Summary:
        //     A type representing Unicode character strings.
        String = 16,
        //
        // Summary:
        //     A type representing a time value.
        Time = 17,
        //
        // Summary:
        //     An integral type representing unsigned 16-bit integers with values between
        //     0 and 65535.
        UInt16 = 18,
        //
        // Summary:
        //     An integral type representing unsigned 32-bit integers with values between
        //     0 and 4294967295.
        UInt32 = 19,
        //
        // Summary:
        //     An integral type representing unsigned 64-bit integers with values between
        //     0 and 18446744073709551615.
        UInt64 = 20,
        //
        // Summary:
        //     A variable-length numeric value.
        VarNumeric = 21,
        //
        // Summary:
        //     A fixed-length stream of non-Unicode characters.
        AnsiStringFixedLength = 22,
        //
        // Summary:
        //     A fixed-length string of Unicode characters.
        StringFixedLength = 23,
        //
        // Summary:
        //     A parsed representation of an XML document or fragment.
        Xml = 25,
        //
        // Summary:
        //     Date and time data. Date value range is from January 1,1 AD through December
        //     31, 9999 AD. Time value range is 00:00:00 through 23:59:59.9999999 with an
        //     accuracy of 100 nanoseconds.
        DateTime2 = 26,
        //
        // Summary:
        //     Date and time data with time zone awareness. Date value range is from January
        //     1,1 AD through December 31, 9999 AD. Time value range is 00:00:00 through
        //     23:59:59.9999999 with an accuracy of 100 nanoseconds. Time zone value range
        //     is -14:00 through +14:00.
        DateTimeOffset = 27,
        Clob = 28,
        NClob = 29,
        Blob = 30,
        Custom = 31,
    }

	[Serializable]
	public class SqlType
	{
        public readonly DbTypeCode DbType;
		public readonly int? Length;
		public readonly byte? Precision;
		public readonly byte? Scale;
		

        public static readonly SqlType Empty = new SqlType(DbTypeCode.Object, 0);

        public static readonly SqlType Guid = new SqlType(DbTypeCode.Guid);
        public static readonly SqlType Boolean = new SqlType(DbTypeCode.Boolean);
        public static readonly SqlType Byte = new SqlType(DbTypeCode.Byte);
        public static readonly SqlType Currency = new SqlType(DbTypeCode.Currency);
        public static readonly SqlType Date = new SqlType(DbTypeCode.Date);
        public static readonly SqlType DateTime = new SqlType(DbTypeCode.DateTime);
        public static readonly SqlType DateTime2 = new SqlType(DbTypeCode.DateTime2);
        public static readonly SqlType DateTimeOffSet = new SqlType(DbTypeCode.DateTimeOffset);
        public static readonly SqlType Decimal = new SqlType(DbTypeCode.Decimal);
        public static readonly SqlType Double = new SqlType(DbTypeCode.Double);
        public static readonly SqlType Int16 = new SqlType(DbTypeCode.Int16);
        public static readonly SqlType Int32 = new SqlType(DbTypeCode.Int32);
        public static readonly SqlType Int64 = new SqlType(DbTypeCode.Int64);
        public static readonly SqlType SByte = new SqlType(DbTypeCode.SByte);
        public static readonly SqlType Single = new SqlType(DbTypeCode.Single);
        public static readonly SqlType Time = new SqlType(DbTypeCode.Time);
        public static readonly SqlType UInt16 = new SqlType(DbTypeCode.UInt16);
        public static readonly SqlType UInt32 = new SqlType(DbTypeCode.UInt32);
        public static readonly SqlType UInt64 = new SqlType(DbTypeCode.UInt64);
        public static readonly SqlType String = new StringSqlType(DbConfig.DefaultLength);
        public static readonly AnsiStringSqlType AnsiString = new AnsiStringSqlType(DbConfig.DefaultLength);
        public static readonly ClobSqlType Clob = new ClobSqlType();
        public static readonly NClobSqlType NClob = new NClobSqlType();
        public static readonly BinarySqlType Binary = new BinarySqlType();
        public static readonly BlobSqlType Blob = new BlobSqlType();

        private static readonly IDictionary<string, SqlType> SqlTypes =
            new Dictionary<string, SqlType>(new Dictionary<string, SqlType>(128));

        public static readonly SqlType[] NoTypes = new SqlType[0];

        private static T GetTypeWithLen<T>(int length, Func<int,SqlType> createDelegate) where T : SqlType
        {
            string key = GetKeyForLengthBased(typeof(T).Name, length);
            SqlType result;
            if (!SqlTypes.TryGetValue(key, out result))
            {
                lock (SqlTypes)
                {
                    if (!SqlTypes.TryGetValue(key, out result))
                    {
                        result = createDelegate(length);
                        SqlTypes.Add(key, result);
                    }
                }
            }
            return (T)result;
        }

        private static SqlType GetTypeWithPrecision(DbTypeCode dbType, byte precision, byte scale)
        {
            string key = GetKeyForPrecisionScaleBased(dbType.ToString(), precision, scale);
            SqlType result;
            if (!SqlTypes.TryGetValue(key, out result))
            {
                result = new SqlType(dbType, precision, scale);
                SqlTypes.Add(key, result);
            }
            return result;
        }

        public static AnsiStringSqlType GetAnsiString(int length)
        {
            return GetTypeWithLen<AnsiStringSqlType>(length, l => new AnsiStringSqlType(l));
        }

        public static BinarySqlType GetBinary(int length)
        {
            return GetTypeWithLen<BinarySqlType>(length, l => new BinarySqlType(l));
        }

        public static StringSqlType GetString(int length)
        {
            return GetTypeWithLen<StringSqlType>(length, l => new StringSqlType(l));
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public static SqlType GetSqlType(DbTypeCode dbType, byte precision, byte scale)
        {
            return GetTypeWithPrecision(dbType, precision, scale);
        }

        private static string GetKeyForLengthBased(string name, int length)
        {
            return name + "(" + length + ")";
        }

        private static string GetKeyForPrecisionScaleBased(string name, byte precision, byte scale)
        {
            return name + "(" + precision + ", " + scale + ")";
        }

        readonly int hashCode;
        readonly string str;

        #region ctor
        public SqlType(DbTypeCode dbType)
		{
			this.DbType = dbType;
            hashCode = dbType.GetHashCode();

            str = dbType.ToString();
		}

        public SqlType(DbTypeCode dbType, int length)
		{
			this.DbType = dbType;
			this.Length = length;

            unchecked
            {
                hashCode = (dbType.GetHashCode() / 2) + (length.GetHashCode() / 2);
            }

            str = string.Format("{0}(Length={1})", dbType, length);
		}

        public SqlType(DbTypeCode dbType, byte precision, byte scale)
		{
			this.DbType = dbType;
			this.Precision = precision;
			this.Scale = scale;
			
            unchecked
            {
                hashCode = (dbType.GetHashCode() / 3) + (precision.GetHashCode() / 3) + (scale.GetHashCode() / 3);
            }
            str = string.Format("{0}(Precision={1},Scale={2})", dbType, precision,scale);
		}
        #endregion

        #region System.Object Members

        public override int GetHashCode()
		{
            return hashCode;
		}

		public override bool Equals(object obj)
		{
			return obj == this || Equals(obj as SqlType);
		}

		public bool Equals(SqlType other)
		{
			if (other == null)
                return false;
            return hashCode == other.hashCode;
		}

		public override string ToString()
		{
            return str;
		}

		#endregion
	}
  
    [Serializable]
    public class AnsiStringSqlType : SqlType
    {
        public AnsiStringSqlType(int length)
            : base(DbTypeCode.AnsiString, length)
        {
        }
    }

    [Serializable]
    public class ClobSqlType : SqlType
    {
        public ClobSqlType()
            : base(DbTypeCode.Clob)
        {
        }
    }

    [Serializable]
    public class StringSqlType : SqlType
    {
        public StringSqlType(int length)
            : base(DbTypeCode.String, length)
        {
        }
    }

    [Serializable]
    public class NClobSqlType : SqlType
    {
       public NClobSqlType()
            : base(DbTypeCode.NClob)
        {
        }
    }
    
    [Serializable]
    public class BinarySqlType : SqlType
    {
        public BinarySqlType()
            : base(DbTypeCode.Binary)
        {
        }

        public BinarySqlType(int length)
            : base(DbTypeCode.Binary, length)
        {
        }
    }

    [Serializable]
    public class BlobSqlType : SqlType
    {
        public BlobSqlType()
            : base(DbTypeCode.Blob)
        {
        }
    }
}
